#include "precompiled.h"
#include "serialization_test.h"

bool g_merging;

namespace unittest
{

template <typename T1, typename T2>
typename boost::enable_if<bond::is_container<T1>, bool>::type 
MergedEqual(const T1& payload, const T1& merged, const T2& obj);

template <typename T1, typename T2>
typename boost::disable_if<bond::is_container<T1>, bool>::type 
MergedEqual(const T1&, const T1&, const T2&)
{
    return false;
}

template <typename P, typename T1, typename T2, typename T3, typename T4>
bool MergedEqual(bond::const_enumerator<P>&, const P& map, const std::pair<T1, T2>& p1, const std::pair<T3, T4>& p2)
{
    if (!Equal(p1.first, p2.first))
        return false;

    if (map.end() != map.find(p1.first))
        return MergedEqual(map.find(p1.first)->second, p1.second, p2.second);
    else
        return MergedEqual(typename bond::element_type<P>::type::second_type(), p1.second, p2.second);
}


template <typename P, typename T1, typename T2>
bool MergedEqual(bond::const_enumerator<P>& items, const P&, const T1& i1, const T2& i2)
{
    if (items.more())
        return MergedEqual(items.next(), i1, i2);
    else
        return MergedEqual(typename bond::element_type<P>::type(), i1, i2);
}

template <typename T1, typename T2>
typename boost::enable_if<bond::is_container<T1>, bool>::type 
MergedEqual(const T1& payload, const T1& merged, const T2& obj)
{
    if (container_size(obj) != container_size(merged))
        return false;

    bond::const_enumerator<T1> payload_items(payload);
    bond::const_enumerator<T1> merged_items(merged);
    bond::const_enumerator<T2> obj_items(obj);

    while (merged_items.more())
        if (!MergedEqual(payload_items, payload, merged_items.next(), obj_items.next()))
            return false;

    return true;
}

bool MergedEqual(const SimpleListsStruct& payload, const SimpleListsStruct& merged, const SimpleListsStructView& obj)
{
    return merged.l_bool == obj.l_bool
        && merged.l_int64 == obj.l_int64
        && merged.l_float == obj.l_float
        && merged.l_string == obj.l_string
        && merged.v_int16 == obj.v_int16
        && merged.v_double == obj.v_double
        && merged.v_string == obj.v_string
        && merged.s_uint64 == obj.s_uint64
        && merged.s_string == obj.s_string
        && merged.m_int8_string == obj.m_int8_string
        && merged.m_string_bool == obj.m_string_bool

        && merged.l_uint32 == payload.l_uint32
        && merged.l_int16 == payload.l_int16
        && merged.v_bool == payload.v_bool
        && merged.v_uint8 == payload.v_uint8
        && merged.v_int64 == payload.v_int64
        && merged.s_bool == payload.s_bool
        && merged.s_float == payload.s_float
        && merged.m_float_uint16 == payload.m_float_uint16;
}


bool MergedEqual(const NestedStruct& payload, const NestedStruct& merged, const NestedStructView& obj)
{
    return merged.m_int8 == obj.m_int8
        && merged.n1 == obj.n1
        && merged.m_int16 == obj.m_int16

        && merged.n3 == payload.n3
        && merged.m_bool == payload.m_bool
        && merged.n2 == payload.n2
        && merged.m_int32 == payload.m_int32
        && merged.m_int64 == payload.m_int64
        && merged.m_uint8 == payload.m_uint8
        && merged.m_uint16 == payload.m_uint16
        && merged.m_uint32 == payload.m_uint32
        && merged.m_uint64 == payload.m_uint64
        && merged.m_double == payload.m_double
        && merged.m_float == payload.m_float
        && merged.m_enum1 == payload.m_enum1
        && merged.m_str == payload.m_str;
}


bool MergedEqual(const NestedMaps& payload, const NestedMaps& merged, const NestedMapsView& obj)
{
    return Equal(payload.m64ls, merged.m64ls)

        && MergedEqual(payload.msSLS, merged.msSLS, obj.msSLS)
        && MergedEqual(payload.m32lNS, merged.m32lNS, obj.m32lNS);
}


bool MergedEqual(const NestedListsStruct& payload, const NestedListsStruct& merged, const NestedListsView& obj)
{
    return Equal(payload.ll8, merged.ll8)
        && Equal(payload.lvls, merged.lvls)
        && Equal(payload.SLS, merged.SLS)
        && Equal(payload.vf, merged.vf)
        && Equal(payload.vss, merged.vss)
        && Equal(payload.lsb, merged.lsb)
        && Equal(payload.m64ls, merged.m64ls)
        && Equal(payload.vmds, merged.vmds)

        && MergedEqual(payload.lSLS, merged.lSLS, obj.lSLS)
        && MergedEqual(payload.vlSLS, merged.vlSLS, obj.vlSLS)
        && MergedEqual(payload.vvNS, merged.vvNS, obj.vvNS);
}

} // namespace unittest


class ModifyContainers
    : public bond::ModifyingTransform
{
public:
    void Begin(const bond::Metadata&) const
    {}

    void End() const
    {}

    void UnknownEnd() const
    {}

    template <typename T>
    bool Base(T& base) const
    {
        return false;
    }

    template <typename T>
    typename boost::enable_if<bond::is_container<T>, bool>::type
    Field(uint16_t /*id*/, const bond::Metadata& /*metadata*/, T& field) const
    {
        Modify(field);
        return false;
    }

    template <typename T>
    typename boost::disable_if<bond::is_container<T>, bool>::type
    Field(uint16_t /*id*/, const bond::Metadata& metadata, T& field) const
    {
        return false;
    }

private:
    template <typename T1, typename T2>
    void Modify(std::pair<T1, T2>& pair) const
    {
        Modify(pair.second);
    }


    template <typename T>
    typename boost::enable_if<bond::is_container<T> >::type
    Modify(T& x) const
    {
        for (bond::enumerator<T> items(x); items.more();)
            Modify(items.next());
    }


    template <typename T>
    typename boost::disable_if<bond::is_container<T> >::type
    Modify(T& x) const
    {
        x = InitRandom<T>();
    }
};


template <typename Reader, typename Writer, typename T1, typename T2>
void ModifyAndMergeContainers(const ModifyContainers& transform)
{
    T1 payload = InitRandom<T1>();

    T2 obj;
    
    bond::Deserialize(Serialize<Reader, Writer>(payload), obj);

    Apply(transform, obj);

    Merging<Reader, Writer>(payload, obj, bond::v1, false);
    Merging<Reader, Writer>(payload, obj, Reader::version, false);
}


template <typename Reader, typename Writer, typename T1, typename T2>
TEST_CASE_BEGIN(MergingContainers)
{
    ModifyAndMergeContainers<Reader, Writer, T1, T2>(ModifyContainers());
}
TEST_CASE_END


template <typename Reader, typename Writer, typename T1, typename T2>
TEST_CASE_BEGIN(MergingAll)
{
    AllMerging<Reader, Writer, T1, T2>();
    AllMerging<Reader, Writer, T2, T1>();
}
TEST_CASE_END


template <typename Reader, typename Writer, typename T1, typename T2>
TEST_CASE_BEGIN(MergingInitialized)
{
    MergingRandom<Reader, Writer, T1, T2>();
    MergingRandom<Reader, Writer, T2, T1>();
}
TEST_CASE_END


template <uint16_t N, typename Reader, typename Writer>
void MergeTests(const char* name)
{
    UnitTestSuite suite(name);

    AddTestCase<TEST_ID(N), 
        MergingAll, Reader, Writer, NestedStructView, NestedStruct>(suite, "Merging nested struct");

    AddTestCase<TEST_ID(N), 
        MergingInitialized, Reader, Writer, NestedStructBondedView, NestedStruct>(suite, "Merging bonded nested struct");

    AddTestCase<TEST_ID(N), 
        MergingAll, Reader, Writer, NestedWithBaseView, NestedWithBase>(suite, "Merging struct with base");

    AddTestCase<TEST_ID(N), 
        MergingAll, Reader, Writer, SimpleListsStructView, SimpleListsStruct>(suite, "Merging simple containers");

    AddTestCase<TEST_ID(N), 
        MergingAll, Reader, Writer, NestedListsStructBondedViewView, NestedListsStructBondedView>(suite, "Merging bonded containers");

    AddTestCase<TEST_ID(N), 
        MergingContainers, Reader, Writer, NestedListsStruct, NestedListsView>(suite, "Merging struct lists");

    AddTestCase<TEST_ID(N), 
        MergingContainers, Reader, Writer, NestedMaps, NestedMapsView>(suite, "Merging struct maps");
}


void SerializationTest::MergeTestsInit()
{
    TEST_COMPACT_BINARY_PROTOCOL(
        MergeTests<
            0x2302,
            bond::CompactBinaryReader<bond::InputBuffer>,
            bond::CompactBinaryWriter<bond::OutputBuffer> >("Merge tests for CompactBinary");
    );

    TEST_FAST_BINARY_PROTOCOL(
        MergeTests<
            0x2303,
            bond::FastBinaryReader<bond::InputBuffer>,
            bond::FastBinaryWriter<bond::OutputBuffer> >("Merge tests for FastBinary");
    );
}
